package test.client.support.thread;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import test.client.thread.BaseThread;
import test.client.thread.BaseThread.Status;
import test.domain.environment.MetaThread;

public class DefaultThreadManager implements ThreadManager, ThreadListener {
	private Log log = LogFactory.getLog(this.getClass());
	
	private Map<Integer, BaseThread> threadCache = new ConcurrentHashMap<Integer, BaseThread>();
	private int maxThreadCount = 0;
	private int completedThreadCount = 0;
	private int startedThreadCount = 0;
	
	private boolean allthreadStarted = false;
	
	private int iterationCount = 0;
	
	/*
	 *  thread generation behavior configuration 
	 */
	private SleepMode sleepMode = SleepMode.thread;
	private RunMode runMode = RunMode.ordered;
	
	private MetaThread meta;
	
	public enum SleepMode {
		loop,thread;
	}
	
	public enum RunMode {
		random,ordered;
	}

	@Override
	public void initialize(MetaThread meta) {
		this.maxThreadCount = meta.getMaxThreadCount();
		if (meta != null) {
			this.meta = meta;
			this.sleepMode = meta.getSleepMode();
			this.runMode = meta.getRunMode();
			
			log.info("ThreadManager: initialized with sleep mode: " + this.sleepMode + ", run mode: " + this.runMode);
			
			if (this.sleepMode == SleepMode.loop) {
				log.info("ThreadManager: loop sleep time: " + meta.getLoopSleepTime() 
					+ " ms, loop max thread: " + meta.getLoopMaxThreadCount());
			}
			else {
				log.info("ThreadManager: thread sleep time: " + meta.getThreadSleepTime() + " ms");
			}
		}
	}
	
	@Override
	public boolean isInitialized() {
		if (maxThreadCount == 0) return false;
		if (threadCache.size() != maxThreadCount)  return false;
		
		return true;
	}
	
	@Override
	public void put(BaseThread thread) {
		threadCache.put(thread.getId(), thread);
	}
	
	@Override
	public Collection<BaseThread> getAll() {
		return threadCache.values();
	}
	
	@Override
	public BaseThread get(int id) {
		return threadCache.get(id);
	}	
	
	@Override
	public void start() {
		this.allthreadStarted = false;
		
		if (!this.isInitialized())
			throw new IllegalStateException("Thread initialization has not yet been completed.");
		
		Map<Integer, BaseThread> threadMap;
		if (this.runMode == RunMode.ordered) {
			threadMap = this.sort(threadCache);	
		}
		else {
			threadMap = this.threadCache;
		}
		
		log.info("ThreadManager: Starting " + this.maxThreadCount + " threads...");		
		for (BaseThread thread : threadMap.values()) {
			this.sleep();
			thread.setStatus(Status.RUNNING);
			new Thread(thread).start();
			this.startedThreadCount++;
			
			log.debug(thread.getId() + "th thread has been started.");
		}
		log.info("ThreadManager: " + this.maxThreadCount + " threads have been started.");
		this.allthreadStarted = true;
	}
	
	@Override
	public void stop() {
		StringBuilder sb = new StringBuilder();		
		int stopped = 0;
		
		for (BaseThread thread : threadCache.values()) {
			if (thread.getStatus() == Status.RUNNING) {
				// print messages from thread
				sb.append(thread.getId() + ": status: " + thread.getStatus() + ", result: " + thread.getResult() 
						+ ", message: " + thread.getMessage() + "\n");
				
				thread.stop();
				stopped++;
			}
		}
		log.info("ThreadManager: " + stopped + " threads have been stopped.\n" + sb.toString());		
	}
	
	public boolean isAllThreadStarted() {
		return this.allthreadStarted;
	}
	
	/*
	 * If a number of running thread reaches at the iteration thread count, sleep the given time.
	 */
	private void sleep(){
		int sleepTime = 0;
		int loopMaxThreadCount = 0;
		
		try {
			if (sleepMode == SleepMode.loop) {
				sleepTime = meta.getLoopSleepTime();
				loopMaxThreadCount = meta.getLoopMaxThreadCount();
				
				if (++iterationCount >= loopMaxThreadCount) {
					log.info("ThreadManager: sleeping..." + this.getStartedThreadCount() + " threads have been started.");
					Thread.sleep(sleepTime);
					iterationCount = 0;
				}
			}
			else {
				sleepTime = meta.getThreadSleepTime();
				Thread.sleep(sleepTime);
			}						
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	@Override
	public boolean isAllThreadDone() {
		if (threadCache.size() == 0) {
			log.debug("ThreadManager: No thread in the cache.");
			return false;
		}
		if (maxThreadCount < threadCache.size()) {
			log.debug("ThreadManager: Thread initialization are not completed yet.");
			return false;
		}
		
		for (BaseThread thread : threadCache.values()) {
			if (thread.getStatus() != Status.COMPLETED) {
				log.debug("ThreadManager: Some threads are not done yet including thread " + thread.getId());
				return false;
			}
		}
		
		return true;
	}

	@Override
	public void printAllThreadStatus() {
		StringBuilder sb = new StringBuilder();
		
		sb.append("ThreadManager: print statuses of all threads...\n");		
		for (BaseThread thread : threadCache.values()) {
			sb.append(thread.getId() + ": " + thread.getStatus() + "\n");
		}
		
		log.info(sb.toString());
	}
	
	private Map<Integer, BaseThread> sort(Map<Integer, BaseThread> source) {
		List<Integer> list = new LinkedList<Integer>(source.keySet());
		
		Collections.sort(list, new Comparator<Integer>() {
			@Override
			public int compare(Integer o1, Integer o2) {
				return (Integer) o1 - (Integer) o2;
			}
		});
		
		Map<Integer, BaseThread> resultMap = new LinkedHashMap<Integer, BaseThread>();
		for (Iterator<Integer> it = list.iterator(); it.hasNext();) {
			Integer key = it.next();
			resultMap.put(key, source.get(key));
		}
		
		return resultMap;
	}

	@Override
	public synchronized void hasStarted() {
		// Do nothing
	}

	@Override
	public synchronized void hasCompleted() {
		this.completedThreadCount++;
	}
	
	@Override
	public int getMaxThreadCount() {
		return this.maxThreadCount;
	}
	
	@Override
	public int getCompletedThreadCount() {
		return this.completedThreadCount;
	}
	
	@Override
	public int getStartedThreadCount() {
		return this.startedThreadCount;
	}
}
